﻿#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>

/*************************
* 常见笔试题
**************************/
//第三题  使用malloc函数开辟动态内存后没有进行free释放
//void GetMemory(char** p, int num)//**p二级指针接收 能找到*str的地址
//{
//	//赋予*p 能通过它找到str 的地址
//    *p = (char*)malloc(num);
//}
//
//void Test(void)
//{
//	//char*str是一级指针
//    char* str = NULL;
//		//&str传参过去要用二级指针接收
//    GetMemory(&str, 100);
//    strcpy(str, "hello");
//    printf(str);
//    free(str); //要进行动态内存释放
//    str = NULL;
//}
//
//int main()
//{
//    Test();
//    return 0;
//}

//第四题   //非法访问动态内存释放后的空间
//void Test(void)
//{
//    char* str = (char*)malloc(100);
//    strcpy(str, "hello");
//    free(str); //把str指向的空间释放还给操作系统，无法继续使用了。str此时已经变野指针了
//    str = NULL;//应该把str正确释放置空指针 下面的不运行
//
//    if (str != NULL)//虽然str已经释放 但是它还是可以找到它的地址，野指针不等于空指针，str确实不等于NULL 会成立
//    {
//        strcpy(str, "world");//str经过释放已经成为野指针了，再对它进行操作会造成非法访问
//        printf(str);
//    }
//}
//
//int main()
//{
//    Test();
//    return 0;
//}

/*******************************************************************
柔性数组
结构中的最后⼀个元素允许是未知⼤⼩的数组，这就叫做『柔性数组』成员
****************************************************************/
//struct S
//{
//	int n;
//	char c;
//	double d;
//	int arr[];//未知大小的数组 - arr就是柔性数组的成员
//};
//有些编译器会报错⽆法编译可以改成：
//struct S2
//{
//	int n;
//	char c;
//	double d;
//	int arr[0];//未知大小的数组 - arr就是柔性数组的成员
//};
/**********************************************************************************
* 柔性数组的特点：
• 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
• sizeof 返回的这种结构⼤⼩不包括柔性数组的内存。
• 包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配，并且分配的内存应该⼤于结构的⼤
⼩，以适应柔性数组的预期⼤⼩。
******************************************************************************/
//示例
#include <stdlib.h>
// struct S
//{
//	int i;//结构中的柔性数组成员前⾯必须⾄少⼀个其他成员
//	int a[0];//柔性数组成员
//};
//int main()
//{
//	printf("%zd\n", sizeof(struct S));//输出的是4 sizeof 返回的这种结构⼤⼩不包括柔性数组的内存
//	return 0;
//}
//第一种应用
//struct S
//{
//	int n;
//	int arr[0];
//};
//int main()
//{
//	//开辟空间
//	//包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配，并且分配的内存应该⼤于结构的⼤⼩，以适应柔性数组的预期⼤⼩。
//	struct S* ps = (struct S*)malloc(sizeof(struct S) + 20 * sizeof(int));//先申请开辟结构体大小 后面多出加上的就是柔性数组的⼤⼩ n 4个字节 arr 80个字节
//	if (ps == NULL)//如果等于空指针 空间开辟失败
//	{
//		perror("malloc");//提示报错信息
//			return 1;//不是有
//	}
//	//使用空间
//	ps->n = 100; //通过ps找到n
//	int i = 0;
//	for (i = 0; i < 20; i++)
//	{
//		ps->arr[i] = i + 1;//arr前20个元素空间赋予1-20数值
//	}
//	//调整ps指向空间的大小
//	//用realloc来拓展ps的空间
//	struct S* tmp = (struct S*)realloc(ps, sizeof(struct S) + 40 * sizeof(int));//柔性数字的好处就是可以柔性、随意调整空间大小
//	if (tmp != NULL)//如果tmp不等于空指针 拓展空间成功空间
//	{
//		ps = tmp;//将拓展的空间赋予ps ps指向结构体 如果开辟成功才赋予 这样做开辟不成功也不会影响ps
//		tmp = NULL;//tmp开辟的空间已经赋予ps了 把tmp置为空指针防止它变为野指针便于维护
//	}
//	else//开辟不成功
//		return 1;
//	for (i = 0; i < 40; i++)
//	{
//		printf("%d  ", ps->arr[i]);//打印出arr的内容 ps存的是结构体的地址，是可以找到结构体中成员arr 前面20个元素已经赋予1-20的值了 后面拓展的空间是随机值
//	}
//	//释放空间
//	free(ps);
//	ps = NULL;
//	return 0;
//}




//第二种应用方法
//struct S
//{
//	int n;
//	int* arr;//变成一个地址
//};
//
//int main()
//{
//	struct S* ps = (struct S*)malloc(sizeof(struct S));//先申请结构体空间大小
//	if (ps == NULL)
//	{
//		perror("malloc");
//		return 1;
//	}
//	int*tmp = (int*)malloc(20*sizeof(int));//给int*arr开辟空间
//	if (tmp != NULL)//不等于空指针就是开辟空间成功
//	{
//		ps->arr = tmp;//把开辟的空间赋予arr
//	}
//	else//开辟失败提前返回
//	{
//		return 1;
//	}
//	ps->n = 100;
//	int i = 0;
//	//给arr中的20个元素赋值为1~20
//	for (i = 0; i < 20; i++)
//	{
//		ps->arr[i] = i + 1;
//	}
//	//调整空间
//	tmp = (int*)realloc(ps->arr, 40*sizeof(int));//给arr拓展空间
//	if (tmp != NULL)
//	{
//		ps->arr = tmp;
//	}
//	else
//	{
//		perror("realloc");
//		return 1;
//	}
//	//
//	for (i = 0; i < 40; i++)
//	{
//		printf("%d ", ps->arr[i]);
//	}
//	//释放
//	free(ps->arr);//先释放arr后开辟的空间 要是先释放ps先开辟的空间 就找不到arr后开辟的了
//	ps->arr = NULL;
//
//	free(ps);
//	ps = NULL;
//
//	return 0;
//}
/****************************************************************************************
上述 代码1 和 代码2 可以完成同样的功能，但是 ⽅法1 的实现有两个好处：
* 第⼀个好处是：⽅便内存释放
	如果我们的代码是在⼀个给别⼈⽤的函数中，你在⾥⾯做了⼆次内存分配，并把整个结构体返回给⽤
	⼾。⽤⼾调⽤free可以释放结构体，但是⽤⼾并不知道这个结构体内的成员也需要free，所以你不能
	指望⽤⼾来发现这个事。所以，如果我们把结构体的内存以及其成员要的内存⼀次性分配好了，并返
	回给⽤⼾⼀个结构体指针，⽤⼾做⼀次free就可以把所有的内存也给释放掉。

*第⼆个好处是：这样有利于访问速度.
	连续的内存有益于提⾼访问速度，也有益于减少内存碎⽚。（其实，我个⼈觉得也没多⾼了，反正你
	跑不了要⽤做偏移量的加法来寻址）
*****************************************************************************************/

/****************************************************************************************
C/C++程序内存分配的⼏个区域：
1. 栈区（stack）：在执⾏函数时，函数内局部变量的存储单元都可以在栈上创建，函数执⾏结束时
这些存储单元⾃动被释放。栈内存分配运算内置于处理器的指令集中，效率很⾼，但是分配的内
存容量有限。 栈区主要存放运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址等。
《函数栈帧的创建和销毁》
2. 堆区（heap）：⼀般由程序员分配释放， 若程序员不释放，程序结束时可能由OS(操作系统)回收 。分配⽅
式类似于链表。
3. 数据段（静态区）：（static）存放全局变量、静态数据。程序结束后由系统释放。
4. 代码段：存放函数体（类成员函数和全局函数）的⼆进制代码。
*****************************************************************************************/




/******************************************************
* 标准流
那为什么我们从键盘输⼊数据，向屏幕上输出数据，并没有打开流呢？
那是因为C语⾔程序在启动的时候，默认打开了3个流：
• stdin - 标准输⼊流，在⼤多数的环境中从键盘输⼊，scanf函数就是从标准输⼊流中读取数据。
• stdout - 标准输出流，⼤多数的环境中输出⾄显⽰器界⾯，printf函数就是将信息输出到标准输出流中。
• stderr - 标准错误流，⼤多数环境中输出到显⽰器界⾯。
这是默认打开了这三个流，我们使⽤scanf、printf等函数就可以直接进⾏输⼊输出操作的。
stdin、stdout、stderr 三个流的类型是： FILE * ，通常称为⽂件指针。
C语⾔中，就是通过 FILE* 的⽂件指针来维护流的各种操作的。
*******************************************************/
//文件操作
//⽂件的打开和关闭
//⽂件在读写之前应该先打开⽂件，在使⽤结束之后应该关闭⽂件。
//在编写程序的时候，在打开⽂件的同时，都会返回⼀个FILE* 的指针变量指向该⽂件，也相当于建⽴了
//指针和⽂件的关系。
//ANSI C 规定使⽤ fopen 函数来打开⽂件， fclose 来关闭⽂件。
////打开⽂件
//FILE* fopen(const char* filename, const char* mode); filename文件名  mode打开模式
////关闭⽂件
//int fclose(FILE* stream);
/************************************************************************************************************
* 文件打开模式
    文件使用方式            含义                                  如果指定文件不存在
  “ r”（只读） 为了输⼊数据，打开⼀个已经存在的⽂本⽂件       出错
   “w”（只写） 为了输出数据，打开⼀个⽂本⽂件                 建⽴⼀个新的⽂件  (如果有这个文件 他就会把里面的内容清空)
   “a”（追加） 向⽂本⽂件尾添加数据							建⽴⼀个新的⽂件
  “rb”（只读） 为了输⼊数据，打开⼀个⼆进制⽂件				出错
  “wb”（只写） 为了输出数据，打开⼀个⼆进制⽂件				建⽴⼀个新的⽂件
  “ab”（追加） 向⼀个⼆进制⽂件尾添加数据						建⽴⼀个新的⽂件
“r + ”（读写） 为了读和写，打开⼀个⽂本⽂件					出错
“w + ”（读写） 为了读和写，建议⼀个新的⽂件					建⽴⼀个新的⽂件
“a + ”（读写） 打开⼀个⽂件，在⽂件尾进⾏读写					建⽴⼀个新的⽂件
“rb + ”（读写）为了读和写打开⼀个⼆进制⽂件					出错
“wb + ”（读写）为了读和写，新建⼀个新的⼆进制⽂件				建⽴⼀个新的⽂件
“ab + ”（读写）打开⼀个⼆进制⽂件，在⽂件尾进⾏读和写			建⽴⼀个新的⽂件
*************************************************************************************************************/

//例子
// #include <stdio.h>
//int main()
//{
//	//打开⽂件
//	//如果打开文件成功 返回的是有效的指针
//	//如果打开失败 则返回的是NULL
//	//FILE* pf = fopen("dechixiaozi.txt", "w");//这是在文件内部的
//	//FILE* pf = fopen("C:\\Users\\Hejinzhuo\\Desktop\\dechi.txt", "w");//这是绝对路径 不在文件内部的 两条斜杠防止被编译为转义字符
//	FILE* pf = fopen(".\\..\\dechi.txt", "w");//这是相对路径 在文件夹内部的 两条斜杠防止被编译为转义字符
//	//.是当前路径  ..是上级路径
//	//⽂件操作
//	if (pf == NULL)
//	{
//		perror("fopen");
//			return 1;
//	}
//	//写文件
//	
//	//关闭⽂件
//	fclose(pf);
//	pf = NULL;//也要把对应指针置空指针
//	return 0;
//}
/***********************************************************
*⽂件的 顺序 读写
函数名			功能					  适⽤于
fgetc		字符输⼊函数(读字符)		所有输⼊流
fputc		字符输出函数(写字符)		所有输出流
fgets		⽂本⾏输⼊函数(读字符串)	所有输⼊流
fputs		⽂本⾏输出函数(写字符串)	所有输出流
fscanf		格式化输⼊函数				所有输⼊流
fprintf		格式化输出函数				所有输出流
上面读和写都是文本信息
fread		⼆进制输⼊					⽂件输⼊流
fwrite		⼆进制输出					⽂件输出流
********************************************************************/
//示例
//写文件
//int main()
//{
//	//打开文件
//	FILE* pf = fopen("dechixiaozi.txt", "w");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//写文件
//	//fputc('a', pf);
//	//fputc('b', pf);
//	//fputc('c', pf);
//	//fputc('d', pf);
//	//fputc('e', pf);
//	for (int i = 'a'; i <= 'z'; i++)
//	{
//		fputc(i, pf);
//	}
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//	return 0;
//}

//读文件
int main()
{
	//打开文件
	FILE* pf = fopen("dechixiaozi.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	//int ch = fgetc(pf);//读取正常的时候，返回读取到的字符的ASLL码值
	//printf("%c\n", ch);//读取失败的时候会返回EOF（-1）
	//ch = fgetc(pf);
	//printf("%c\n", ch);
	//ch = fgetc(pf);
	//printf("%c\n", ch);
	//ch = fgetc(pf);
	//printf("%c\n", ch);
	int ch = 0;
	while ((ch = fgetc(pf)) != EOF)//没读到最后就一直读
	{
		printf("%c ", ch);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}
